#region Copyright Notices
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Library General Public License for more details.
// 
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#endregion

using System;
using System.IO;

namespace Yarr
{
	/// <summary>
	/// Provides a top-level Read-Eval-Print loop
	/// </summary>
	public class TopLoop
    {
        #region Private members
        private Environment environment;
        private const int MAX_RECUR = 1000;

		private string prompt = "> ";
        #endregion


        #region Properties
        /// <summary>
        /// Gets or sets the input prompt
        /// </summary>
        public string Prompt
        {
            get
            {
                return prompt;
            }
            set
            {
                prompt = value;
            }
        }
        #endregion


        #region Constructors
        internal TopLoop()
		{
            environment = Environment.MakeEnvironment();
		}

		internal TopLoop(Environment environment)
		{
			this.environment = environment;
        }
        #endregion


        #region Static methods
        /// <summary>
        /// Creates a new REPL, with a new environment.
        /// </summary>
        public static TopLoop MakeRepl()
        {
            return new TopLoop();
        }

        /// <summary>
        /// Creates a new REPL, with the specified environment.
        /// </summary>
        /// <param name="environment">The environment for this REPL</param>
        public static TopLoop MakeRepl(Environment environment)
        {
            return new TopLoop(environment);
        }
        #endregion


        #region Public methods
        /// <summary>
        /// Starts the REPL. This also binds the following:
        /// 
        /// If *input* is unbound, binds it to Console.In
        /// If *output* is unbound, binds it to Console.Out
        /// If *error* is unbound, binds it to Console.Error
        /// If *max-recursion-depth* is unbound, binds it to 1000
        /// 
        /// Execution ceases when the reader sees EOF in the input stream. 
        /// 
        /// After each read-eval-print iteration, the result of the eval is bound to the symbol ?.
        /// </summary>
		public void Run() 
		{
			Run(Console.In, Console.Out, Console.Error, MAX_RECUR, null, false);
		}

        /// <summary>
        /// Starts the REPL, and attaches an IDebuggerBreak instance to the runtime.
        /// The Yarr debugger is not activated
        /// 
        /// This also binds the following:
        /// 
        /// If *input* is unbound, binds it to Console.In
        /// If *output* is unbound, binds it to Console.Out
        /// If *error* is unbound, binds it to Console.Error
        /// If *max-recursion-depth* is unbound, binds it to 1000
        /// 
        /// Execution ceases when the reader sees EOF in the input stream. 
        /// 
        /// After each read-eval-print iteration, the result of the eval is bound to the symbol ?.
        /// </summary>
        /// <param name="debugger">An IDebuggerBreak instance to attach to runtime</param>
        public void Run (IDebuggerBreak debugger)
        {
            Run(Console.In, Console.Out, Console.Error, MAX_RECUR, debugger, false);
        }

        /// <summary>
        /// Starts the REPL, and attaches an IDebuggerBreak instance to the runtime.
        /// 
        /// This also binds the following:
        /// 
        /// If *input* is unbound, binds it to Console.In
        /// If *output* is unbound, binds it to Console.Out
        /// If *error* is unbound, binds it to Console.Error
        /// If *max-recursion-depth* is unbound, binds it to 1000
        /// 
        /// Execution ceases when the reader sees EOF in the input stream. 
        /// 
        /// After each read-eval-print iteration, the result of the eval is bound to the symbol ?.
        /// </summary>
        /// <param name="debugger">An IDebuggerBreak instance to attach to runtime</param>
        /// <param name="debuggerActive">If true, the Yarr debugger is activated before the REPL executes</param>
        public void Run(IDebuggerBreak debugger, bool debuggerActive)
        {
            Run(Console.In, Console.Out, Console.Error, MAX_RECUR, debugger, debuggerActive);
        }

		/// <summary>
        /// Starts the REPL, and attaches an IDebuggerBreak instance to the runtime.
        /// 
        /// This also binds the following:
        /// 
        /// If *input* is unbound, binds it to <paramref name="reader"/>
        /// If *output* is unbound, binds it to <paramref name="writer"/>
        /// If *error* is unbound, binds it to <paramref name="error"/>
        /// If *max-recursion-depth* is unbound, sets it to <paramref name="recurDepth"/>
        ///  
        /// Execution ceases when the reader sees EOF in the input stream. 
        /// 
        /// After each read-eval-print iteration, the result of the eval is bound to the symbol ?.
        /// </summary>
		/// <param name="reader">The reader for input</param>
		/// <param name="writer">The writer for output</param>
		/// <param name="error">The writer for errors</param>
        /// <param name="recurDepth">Max recursion depth</param>
        /// <param name="debugger">IDebuggerBreak instance to attach to the runtime</param>
        /// <param name="debuggerActive">If true, the Yarr debugger is activated before the REPL executes</param>
        public void Run(TextReader reader, TextWriter writer, TextWriter error, int recurDepth, 
                        IDebuggerBreak debugger, bool debuggerActive) 
		{
            environment[Symbol.INPUT] = reader;
            environment[Symbol.OUTPUT] = writer;
            environment[Symbol.ERROR] = error;

            if (!environment.Contains(Symbol.MAX_RECUR_DEPTH))
                environment[Symbol.MAX_RECUR_DEPTH] = 1000;

            Symbol LAST = Symbol.FromName("?");

            if (debugger != null)
                Runtime.AttachDebugger(debugger);

			while (true) 
			{
				try 
				{
                    if (writer != null)
					    writer.Write(prompt);

                    if (debugger != null && debuggerActive)
                        Runtime.EnableDebugger(DebuggerMode.RunToBreakpoint);

                    Object o = Runtime.Eval(Runtime.Read(null, null, environment), environment);

                    environment.AssignLocal(LAST, o);
                    if (writer != null)
                        writer.WriteLine(Printer.WriteToString(o));
				} 
				catch (YarrException e) 
				{
                    if (error != null)
					    error.WriteLine(e.Message);
				}
				catch (Exception e) 
				{
                    if (error != null)
					    error.WriteLine(e);
				}
			}
        }
        #endregion
    }
}
